﻿using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using Medianamik.Core.Sugar;
using NLog;
using NLog.Config;
using System.Configuration;
using Medianamik.Core.Configuration;

namespace Medianamik.Core.Logging
{
    public class ActivityLogger
    {
        protected class InnerLog
        {
            public ILoggable Log { get; private set; }
            public string User { get; private set; }
            public string Message { get; private set; }
            public DateTime LoggedOn { get; private set; }
            public string IP { get; private set; }
            public string UserAgent { get; private set; }

            public InnerLog(ILoggable log, string message, string user, string ip, string useragent)
            {
                Log = log;
                User = user;
                Message = message;
                LoggedOn = DateTime.Now;
                IP = ip;
                UserAgent = useragent;
            }
        }
        private class AppStateLocker : IDisposable
        {
            public HttpApplicationState State { get; private set; }

            public AppStateLocker(HttpApplicationState state)
            {
                State = state;
                State.Lock();
            }

            public void Dispose()
            {
                State.UnLock();
            }
        }
        private static Logger _logger;
        protected static Logger Logger
        {
            get
            {
                return _logger
                    ?? (_logger = LogManager.GetLogger("ActivityLogger"));
            }
        }

        private static List<LogEntryType> _ignoredTypes;
        protected static List<LogEntryType> IgnoredTypes
        {
            get
            {
                if (_ignoredTypes == null)
                {
                    _ignoredTypes = new List<LogEntryType>();
                    // TODO: Remplir la liste
                }
                return _ignoredTypes;
            }
        }

        #region Log Cache
        private static Stack<InnerLog> GetLogCache(HttpApplication pApplication)
        {
            return GetCache(pApplication, "Logs");
        }
        private static Stack<InnerLog> GetLogPendingCache(HttpApplication pApplication)
        {
            return GetCache(pApplication, "PendingLogs");
        }
        private static Stack<InnerLog> GetCache(HttpApplication pApplication, string cacheName)
        {
            var state = pApplication.Application;
            if (state[cacheName] == null)
            {
                using (var locker = new AppStateLocker(state))
                {
                    if (state[cacheName] == null)
                    {
                        state[cacheName] = new Stack<InnerLog>();
                    }
                }
            }
            var cache = (Stack<InnerLog>) state[cacheName];
            return cache;
        }
        private static void SwapCaches(HttpApplication pApplication)
        {
            var state = pApplication.Application;
            using(var locker = new AppStateLocker(state))
            {
                var tmp = state["Logs"];
                state["Logs"] = state["PendingLogs"];
                state["PendingLogs"] = tmp;
            }
        }

        private static readonly int _logCacheSize;
        private static readonly object _lock = new object();
        private static void AddToStack(HttpApplication app, ILoggable log, string message, string user)
        {
            var innerLog = new InnerLog(log, message, user, HttpContext.Current.Request.UserHostAddress, HttpContext.Current.Request.UserAgent);
            if (_logCacheSize > 0)
            {
                lock (_lock)
                {
                    GetLogCache(app).Push(innerLog);
                }
                var cache = GetLogCache(app);
                if (cache.Count > _logCacheSize)
                    SaveLogs(app);
            }
            else
                SaveLog(innerLog);            
        }       
        internal static void SaveLogs(HttpApplication pApplication)
        {
            SaveLogs(pApplication, false);
        }
        internal static void SaveLogs(HttpApplication pApplication, bool waitForEnd)
        {
            var saver = new LogSaver(pApplication);
            LogSaver.SaveLogs(saver, waitForEnd);
        }

        internal static void SaveLogsToDb(HttpApplication pApplication)
        {
            SwapCaches(pApplication);
            var pendingCache = GetLogPendingCache(pApplication);
            while (pendingCache.Count > 0)
            {
                SaveLog(pendingCache.Pop());
            }
        }
        private static void SaveLog(InnerLog log)
        {
            Logger.Info(log.Message, log.Log.LogObjectID, log.Log.LogObjectName, log.User, log.Log.LogType, log.IP, log.UserAgent);
        }
        #endregion

        static ActivityLogger()
        {
            //Création et configuration du logger
            var target = new DatabaseTarget();
            LogManager.Configuration.AddTarget("db", target);

            var rule = new LoggingRule("ActivityLogger", LogLevel.Info, target);
            LogManager.Configuration.LoggingRules.Add(rule);
            _enableLogging =
            ((MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK)).
                EnableLogging;
            _logCacheSize =
                ((MedianamikConfigSection) ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK)).
                    LogCacheSize;
        }

        private static readonly bool _enableLogging;

        public void Log(ILoggable pLoggable, string pMessage)
        {
            Log(pLoggable, pMessage, Utility.GetCurrentUserName());
        }

        public void Log(ILoggable pLoggable, string pMessage, string pUser)
        {
            var doLog = _enableLogging && !IgnoredTypes.Any(type => type == pLoggable.LogType);
            if (doLog)
            {
                var n = pLoggable as Node;
                if (n != null)
                    doLog = !n.NodeType.IsManyToMany;
                if (doLog)
                {
                    if (HttpContext.Current != null)
                    {
                        AddToStack(HttpContext.Current.ApplicationInstance, pLoggable, pMessage, pUser);
                    }
                }
            }
        }
    }
}
